package time.providers.sql.query;
import haxe.macro.Expr;
import time.providers.sql.query.SqlGenerator;
import time.compiler.query.Data;
import time.compiler.query.solvers.Build;
import time.compiler.types.Data;
import time.compiler.query.Solver;
import time.Errors;
import time.providers.sql.query.Data;
import time.providers.sql.query.Solvers;

/**
 * This is the main To-Many solver for SQL
 * @author waneck
 */

@:timeApi(DBToMany($A), 
{
	function map<B>(fn:A->B):DBToMany<B> {}
	function filter(fn:A->Bool):DBToMany<A> {}
//	function mapFilter<B>(fn:A->Null<B>):Query<B>; //TODO
	function sum(field:A->Float):Float {}
	function avg(field:A->Float):Float {}
	function max(field:A->Float):Float {}
	function min(field:A->Float):Float {}
	function count():Int {}
	function orderBy(field:A->Float, asc:Bool, ?repeat):DBToMany<A> {} //TODO
	//function view<Key,Value>(fn:A->{key:Key, value:Value}):DatabaseView<Key, Value>; //TODO
	function exists(fn:A->Bool):Bool {}
	function all(fn:A->Bool):Bool {}
	function first<A>():A {}
	function last<A>():A {}
	function get(index:Int):A {}
})
class SqlToManySolver extends SqlSolver
{

	public function new()
	{
		super();
		
		Build.solver();
	}
	
	override public function handleCall(e1:SqlExpr, s:String, ecall:Array<QueryExpr>, callType:DBType, gen:SqlGenerator):SqlExpr
	{
		return switch(s)
		{
			case "map":
				switch(ecall[0].expr)
				{
					case QFunction(f):
						gen.pushBlock(); //intializing a new var context
						
						//setting the argument as a variable with value "self"
						gen.newVar({variable:f.args[0].name, expr:e1});
						var emap = gen.parseToExpr(f.expr);
						gen.setSelectCandidate(emap);
						
						gen.popBlock();
						emap;
					default: throw Assert;
				}
			case "filter":
				switch(ecall[0].expr)
				{
					case QFunction(f):
						gen.pushBlock(); //intializing a new var context
						
						//setting the argument as a variable with value "self"
						gen.newVar({variable:f.args[0].name, expr:e1});
						var emap = gen.parseToExpr(f.expr);
						gen.addCondition(emap);
						
						gen.popBlock();
						e1;
					default: throw Assert;
				}
			case "sum", "avg", "max", "min":
				switch(ecall[0].expr)
				{
					case QFunction(f):
						gen.pushBlock(); //intializing a new var context
						//gen.beginGroupBy(
						
						//setting the argument as a variable with value "self"
						gen.newVar({variable:f.args[0].name, expr:e1});
						var emap = gen.parseToExpr(f.expr);
						
						//validate emap, need VColumn or VStruct. Or need 
						var vvar = getFieldAccess(emap, ecall[0].pos);
						var vself = getFieldAccess(e1, gen.pos);
						var fun = switch(s)
						{
							case "sum": ASum;
							case "avg": AAvg;
							case "min": AMin;
							case "max": AMax;
							//case "count": ACount;
						};
						
						var vname = gen.setAggregator([vself], fun, vvar);
						
						gen.popBlock();
						SValue(VVariable((vname)));
					default: throw Assert;
				}
			case "count":
				var vself = getFieldAccess(e1, gen.pos);
				
				var vname = gen.setAggregator([vself], ACount, null);
				SValue(VVariable(VName(vname)));
			case "orderBy":
				var len = ecall.length;
				var i = 0;
				while (i < len)
				{
					switch(ecall[i].expr)
					{
						case QFunction(f):
							gen.pushBlock(); //intializing a new var context

							//setting the argument as a variable with value "self"
							gen.newVar({variable:f.args[0].name, expr:e1});
							var emap = gen.parseToExpr(f.expr);

							//validate emap, need VColumn or VStruct. Or need 
							var vvar = getFieldAccess(emap, ecall[0].pos);
							
							var asc = switch(ecall[i+1].expr)
							{
								case QConst(c):
									switch(c)
									{
										case QCBool(b):
											b;
										default: throw Assert;
									}
								default: throw Assert;
							};
							
							gen.addOrderBy(vvar, (asc) ? SASC : SDESC);
							
							gen.popBlock();
						default: throw Assert;
					}
					
					i += 2;
				}
				
				e1; //return self
			case "exists":
				switch(ecall[0].expr)
				{
					case QFunction(f):
						gen.pushBlock(); //intializing a new var context
						
						//setting the argument as a variable with value "self"
						gen.newVar({variable:f.args[0].name, expr:e1});
						var emap = gen.parseToExpr(f.expr);
						gen.addCondition(emap);
						
						gen.popBlock();
						e1;
					default: throw Assert;
				}
		}
	}
	
	function getFieldAccess(e:SqlExpr, pos:Position) : Variable
	{
		return switch(e)
		{
			case SValue(v):
				switch(v)
				{
					case VVariable(v):
						switch(v)
						{
							case VColumn(_,_), VStruct(_,_,_): vvar = v;
							default: gen.error("Illogical use of variable " + v + " on aggregation", pos);
						}
					default: gen.error("Only field access is allowed on 'field' property", pos);
				}
			default: gen.error("Only field access is allowed on 'field' property", pos);
		};
	}
	
}